package client.inventory;

import constants.ItemConstants;
import server.MapleItemInformationProvider;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MapleInventory implements Iterable<Item>, Serializable {


    private static final long serialVersionUID = -7238868473236710891L;
    private transient final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private MapleInventoryType type;
    private short slotLimit = 0;
    private Map<Short, Item> inv = new LinkedHashMap<>();

    /**
     * Creates a new instance of MapleInventory
     */
    public MapleInventory() {
    }

    /**
     * Creates a new instance of MapleInventory
     */
    public MapleInventory(MapleInventoryType type) {
        this.type = type;
    }

    public MapleInventory copy() {
        MapleInventory ret = new MapleInventory();
        ret.type = type;
        ret.slotLimit = slotLimit;
        ret.inv = new LinkedHashMap<>();
        lock.readLock().lock();
        try {
            inv.forEach((key, value) -> ret.inv.put(key, value));
        } finally {
            lock.readLock().unlock();
        }
        return ret;
    }

    public void addSlot(short slot) {
        this.slotLimit += slot;
        if (slotLimit > 128) {
            slotLimit = 128;
        }
    }

    public short getSlotLimit() {
        return slotLimit;
    }

    public void setSlotLimit(short slot) {
        if (slot > 128) {
            slot = 128;
        }
        slotLimit = slot;
    }

    /**
     * Returns the item with its slot id if it exists within the inventory,
     * otherwise null is returned
     */
    public Item findById(int itemId) {
        lock.readLock().lock();
        try {
            for (Item item : inv.values()) {
                if (item.getItemId() == itemId) {
                    return item;
                }
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public Item findByUniqueId(int itemId) {
        lock.readLock().lock();
        try {
            for (Item item : inv.values()) {
                if (item.getUniqueId() == itemId) {
                    return item;
                }
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public Item findByInventoryId(long onlyId, int itemId) {
        lock.readLock().lock();
        try {
            for (Item item : inv.values()) {
                if (item.getInventoryId() == onlyId && item.getItemId() == itemId) {
                    return item;
                }
            }
            return findById(itemId);
        } finally {
            lock.readLock().unlock();
        }
    }

    public Item findByEquipOnlyId(long onlyId, int itemId) {
        lock.readLock().lock();
        try {
            for (Item item : inv.values()) {
                if (item.getEquipOnlyId() == onlyId && item.getItemId() == itemId) {
                    return item;
                }
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public int countById(int itemId) {
        lock.readLock().lock();
        try {
            int possesed = 0;
            for (Item item : inv.values()) {
                if (item.getItemId() == itemId) {
                    possesed += item.getQuantity();
                }
            }
            return possesed;
        } finally {
            lock.readLock().unlock();
        }
    }

    public List<Item> listById(int itemId) {
        lock.readLock().lock();
        try {
            List<Item> ret = new ArrayList<>();
            for (Item item : inv.values()) {
                if (item.getItemId() == itemId) {
                    ret.add(item);
                }
            }
            if (ret.size() > 1) {
                Collections.sort(ret);
            }
            return ret;
        } finally {
            lock.readLock().unlock();
        }
    }

    /*
     * 新增函数
     */
    public List<Item> listByEquipOnlyId(int equipOnlyId) {
        lock.readLock().lock();
        try {
            List<Item> ret = new ArrayList<>();
            for (Item item : inv.values()) {
                if (item.getEquipOnlyId() > 0 && item.getEquipOnlyId() == equipOnlyId) {
                    ret.add(item);
                }
            }
            if (ret.size() > 1) {
                Collections.sort(ret);
            }
            return ret;
        } finally {
            lock.readLock().unlock();
        }
    }

    public Collection<Item> list() {
        lock.readLock().lock();
        try {
            return inv.values();
        } finally {
            lock.readLock().unlock();
        }
    }

    public List<Item> newList() {
        lock.readLock().lock();
        try {
            if (inv.size() <= 0) {
                return Collections.emptyList();
            }
            return new LinkedList<>(inv.values());
        } finally {
            lock.readLock().unlock();
        }
    }

    public List<Integer> listIds() {
        lock.readLock().lock();
        try {
            List<Integer> ret = new ArrayList<>();
            for (Item item : inv.values()) {
                if (!ret.contains(item.getItemId())) {
                    ret.add(item.getItemId());
                }
            }
            if (ret.size() > 1) {
                Collections.sort(ret);
            }
            return ret;
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * Adds the item to the inventory and returns the assigned slot id
     */
    public short addItem(Item item) {
        short slotId = getNextFreeSlot();
        if (slotId < 0) {
            return -1;
        }
        lock.writeLock().lock();
        try {
            inv.put(slotId, item);
            item.setPosition(slotId);
            return slotId;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void addFromDB(Item item) {
        lock.writeLock().lock();
        try {
            if (item.getPosition() < 0 && !type.equals(MapleInventoryType.EQUIPPED)) {
                // This causes a lot of stuck problem, until we are done with position checking
                return;
            }
            if (item.getPosition() > 0 && type.equals(MapleInventoryType.EQUIPPED)) {
                // This causes a lot of stuck problem, until we are done with position checking
                return;
            }
            inv.put(item.getPosition(), item);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void move(short sSlot, short dSlot, short slotMax) {
        lock.writeLock().lock();
        try {
            Item source = inv.get(sSlot);
            Item target = inv.get(dSlot);
            if (source == null) {
                throw new InventoryException("Trying to move empty slot");
            }
            if (target == null) {
                if (dSlot < 0 && !type.equals(MapleInventoryType.EQUIPPED)) {
                    // This causes a lot of stuck problem, until we are done with position checking
                    return;
                }
                if (dSlot > 0 && type.equals(MapleInventoryType.EQUIPPED)) {
                    // This causes a lot of stuck problem, until we are done with position checking
                    return;
                }
                source.setPosition(dSlot);
                inv.put(dSlot, source);
                inv.remove(sSlot);
            } else if (target.getItemId() == source.getItemId() && !ItemConstants.isRechargable(source.getItemId()) && target.getOwner().equals(source.getOwner()) && target.getExpiration() == source.getExpiration() && target.getFamiliarCard() == null && source.getFamiliarCard() == null) {
                if (type.getType() == MapleInventoryType.EQUIP.getType() || type.getType() == MapleInventoryType.CASH.getType()) {
                    swap(target, source);
                } else if (source.getQuantity() + target.getQuantity() > slotMax) {
                    source.setQuantity((short) ((source.getQuantity() + target.getQuantity()) - slotMax));
                    target.setQuantity(slotMax);
                } else {
                    target.setQuantity((short) (source.getQuantity() + target.getQuantity()));
                    inv.remove(sSlot);
                }
            } else {
                swap(target, source);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void swap(Item source, Item target) {
        lock.writeLock().lock();
        try {
            inv.remove(source.getPosition());
            inv.remove(target.getPosition());
            short swapPos = source.getPosition();
            source.setPosition(target.getPosition());
            target.setPosition(swapPos);
            inv.put(source.getPosition(), source);
            inv.put(target.getPosition(), target);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public Item getItem(short slot) {
        lock.readLock().lock();
        try {
            return inv.get(slot);
        } finally {
            lock.readLock().unlock();
        }
    }

    public Map<Short, Item> getInventory() {
        lock.readLock().lock();
        try {
            return inv;
        } finally {
            lock.readLock().unlock();
        }
    }

    public void removeItem(short slot) {
        removeItem(slot, (short) 1, false);
    }

    public void removeItem(short slot, short quantity, boolean allowZero) {
        lock.writeLock().lock();
        try {
            Item item = inv.get(slot);
            if (item == null) {
                return;
            }
            item.setQuantity((short) (item.getQuantity() - quantity));
            if (item.getQuantity() < 0) {
                item.setQuantity((short) 0);
            }
            if (item.getQuantity() == 0 && !allowZero) {
                removeSlot(slot);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void removeSlot(short slot) {
        lock.writeLock().lock();
        try {
            inv.remove(slot);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void removeAll() {
        inv.clear();
    }

    public boolean isFull() {
        return inv.size() >= slotLimit;
    }

    public boolean isFull(int margin) {
        return inv.size() + margin >= slotLimit;
    }

    /**
     * Returns the next empty slot id, -1 if the inventory is full
     */
    public short getNextFreeSlot() {
        lock.readLock().lock();
        try {
            if (isFull()) {
                return -1;
            }
            for (short i = 1; i <= slotLimit; i++) {
                if (!inv.containsKey(i)) {
                    return i;
                }
            }
            return -1;
        } finally {
            lock.readLock().unlock();
        }
    }

    public short getNumFreeSlot() {
        lock.readLock().lock();
        try {
            if (isFull()) {
                return 0;
            }
            short free = 0;
            for (short i = 1; i <= slotLimit; i++) {
                if (!inv.containsKey(i)) {
                    free++;
                }
            }
            return free;
        } finally {
            lock.readLock().unlock();
        }
    }

    /*
     * 获取装备中的技能皮肤道具ID信息
     */
    public List<Integer> listSkillSkinIds() {
        List<Integer> ret = new ArrayList<>();
        lock.readLock().lock();
        try {
            for (Item item : inv.values()) {
                if (item.isSkillSkin() && !ret.contains(item.getItemId())) {
                    ret.add(item.getItemId());
                }
            }
            return ret;
        } finally {
            lock.readLock().unlock();
        }
    }

    public final Item getArrowSlot(int n2) {
        lock.readLock().lock();
        try {
            MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
            LinkedList<Item> items = new LinkedList<>();
            for (Item item : inv.values()) {
                if (item.getPosition() <= 128) {
                    items.add(item);
                }
            }
            items.sort(Comparator.naturalOrder());
            for (Item item : items) {
                if (ItemConstants.is弓矢道具(item.getItemId()) && n2 >= ii.getReqLevel(item.getItemId())) {
                    return item;
                }
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public final Item getCrossbowSlot(int n2) {
        lock.readLock().lock();
        try {
            MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
            LinkedList<Item> items = new LinkedList<>();
            for (Item item : inv.values()) {
                if (item.getPosition() > 128) continue;
                items.add(item);
            }
            items.sort(Comparator.naturalOrder());
            for (Item item : items) {
                if (!ItemConstants.is弩矢道具(item.getItemId()) || n2 < ii.getReqLevel(item.getItemId())) continue;
                return item;
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public final Item getBulletSlot(int n2) {
        lock.readLock().lock();
        try {
            MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
            LinkedList<Item> items = new LinkedList<>();
            for (Item item : inv.values()) {
                if (item.getPosition() <= 128) {
                    items.add(item);
                }
            }
            items.sort(Comparator.naturalOrder());
            for (Item item : items) {
                if (!ItemConstants.is子弹道具(item.getItemId()) || n2 < ii.getReqLevel(item.getItemId())) continue;
                return item;
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public final Item getDartsSlot(int n2) {
        lock.readLock().lock();
        try {
            MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
            LinkedList<Item> linkedList = new LinkedList<>();
            for (Item item : inv.values()) {
                if (item.getPosition() <= 128) {
                    linkedList.add(item);
                }
            }
            linkedList.sort(Comparator.naturalOrder());
            for (Item item : linkedList) {
                if (!ItemConstants.is飞镖道具(item.getItemId()) || n2 < ii.getReqLevel(item.getItemId())) continue;
                return item;
            }
            return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    public MapleInventoryType getType() {
        return type;
    }

    @Override
    public Iterator<Item> iterator() {
        return Collections.unmodifiableCollection(inv.values()).iterator();
    }
}
